# -*- coding: utf-8 -*-
"""
监控引擎核心
整合数据采集、规则判断、告警发送
"""

import sys
import os
import time
import logging
from typing import Dict, Any, Set
from datetime import datetime
from threading import Thread, Event
import traceback

# 添加父目录到路径以便导入common模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

from common.alert_manager import alert_manager, Alert, AlertLevel, AlertType
from .data_collector import DataCollector
from .rule_engine import RuleEngine
from storage import DatabaseManager

logger = logging.getLogger(__name__)


class MonitorEngine:
    """监控引擎"""
    
    def __init__(self, config: Dict[str, Any], db_manager: DatabaseManager):
        self.config = config
        self.db_manager = db_manager
        self.data_collector = DataCollector(config)
        self.rule_engine = RuleEngine(config)
        
        # 监控状态
        self.running = False
        self.stop_event = Event()
        self.monitor_thread = None
        
        # 快照缓存
        self.last_snapshot = {
            'databases': set(),
            'tables': set(),
            'table_details': {}
        }
        
        # 表覆盖检测
        self.recent_deletes: Dict[str, float] = {}
        
        # 批量删除检测
        self.recent_delete_list = []
        
        logger.info("监控引擎初始化完成")
    
    def start(self):
        """启动监控引擎"""
        if self.running:
            logger.warning("监控引擎已在运行")
            return
        
        self.running = True
        self.stop_event.clear()
        
        # 启动监控线程
        self.monitor_thread = Thread(target=self._monitor_loop, daemon=True)
        self.monitor_thread.start()
        
        # 更新系统状态
        self.db_manager.update_system_status(
            'monitor_engine', 'running', '监控引擎已启动'
        )
        
        logger.info("监控引擎已启动")
    
    def stop(self):
        """停止监控引擎"""
        if not self.running:
            return
        
        logger.info("正在停止监控引擎...")
        self.running = False
        self.stop_event.set()
        
        if self.monitor_thread:
            self.monitor_thread.join(timeout=10)
        
        # 更新系统状态
        self.db_manager.update_system_status(
            'monitor_engine', 'stopped', '监控引擎已停止'
        )
        
        logger.info("监控引擎已停止")
    
    def _monitor_loop(self):
        """监控循环"""
        interval = self.config.get('monitor', {}).get('interval', 30)
        
        while self.running and not self.stop_event.is_set():
            try:
                # 执行一次监控
                self._run_once()
                
                # 等待下一次监控
                self.stop_event.wait(timeout=interval)
            
            except Exception as e:
                logger.error(f"监控循环异常: {e}", exc_info=True)
                
                # 发送监控异常告警
                try:
                    alert = Alert(
                        alert_type="monitor_error",
                        level=AlertLevel.CRITICAL,
                        database="system",
                        details={'错误信息': str(e), '堆栈': traceback.format_exc()}
                    )
                    alert_manager.send_alert(alert)
                except:
                    pass
                
                # 等待后重试
                self.stop_event.wait(timeout=10)
    
    def _run_once(self):
        """执行一次监控检查"""
        logger.debug("开始执行监控检查...")
        start_time = time.time()
        
        try:
            # 1. 采集当前快照
            current_snapshot = self._collect_snapshot()
            
            # 2. 如果有上次快照，进行对比
            if self.last_snapshot['databases']:
                self._compare_and_alert(current_snapshot)
            
            # 3. 更新快照
            self.last_snapshot = current_snapshot
            
            # 4. 记录指标
            elapsed_time = time.time() - start_time
            self.db_manager.save_metric(
                'monitor_cycle_time', elapsed_time, 'seconds',
                {'component': 'monitor_engine'}
            )
            
            logger.debug(f"监控检查完成，耗时 {elapsed_time:.2f}秒")
        
        except Exception as e:
            logger.error(f"监控检查异常: {e}", exc_info=True)
            raise
    
    def _collect_snapshot(self) -> Dict[str, Any]:
        """采集当前元数据快照"""
        snapshot = {
            'databases': set(),
            'tables': set(),
            'table_details': {}
        }
        
        # 采集数据库列表
        databases = self.data_collector.get_all_databases()
        snapshot['databases'] = set(databases)
        
        # 采集表列表和详情
        tables = self.data_collector.get_all_tables()
        for table in tables:
            db_name = table['db_name']
            table_name = table['table_name']
            table_id = table['table_id']
            full_name = f"{db_name}.{table_name}"
            
            snapshot['tables'].add(full_name)
            
            # 获取表详情
            try:
                details = self.data_collector.get_table_details(table_id)
                details.update({
                    'table_type': table['table_type'],
                    'owner': table['owner'],
                    'create_time': table['create_time']
                })
                snapshot['table_details'][full_name] = details
                
                # 保存快照到数据库
                self.db_manager.save_snapshot(
                    db_name, table_name,
                    partition_count=details.get('partition_count', 0),
                    row_count=details.get('row_count', 0),
                    total_size=details.get('total_size', 0),
                    num_files=details.get('num_files', 0),
                    table_type=details.get('table_type'),
                    owner=details.get('owner')
                )
            except Exception as e:
                logger.error(f"获取表详情失败 {full_name}: {e}")
        
        logger.info(f"采集快照完成: {len(snapshot['databases'])}个数据库, "
                   f"{len(snapshot['tables'])}张表")
        
        return snapshot
    
    def _compare_and_alert(self, current_snapshot: Dict[str, Any]):
        """对比快照并产生告警"""
        # 1. 检查删库
        deleted_dbs = self.last_snapshot['databases'] - current_snapshot['databases']
        if deleted_dbs:
            self._check_drop_database(deleted_dbs)
        
        # 2. 检查删表
        deleted_tables = self.last_snapshot['tables'] - current_snapshot['tables']
        if deleted_tables:
            self._check_drop_table(deleted_tables)
            self._check_batch_delete()
        
        # 3. 检查新增表（可能是表覆盖）
        new_tables = current_snapshot['tables'] - self.last_snapshot['tables']
        if new_tables:
            self._check_table_overwrite(new_tables)
        
        # 4. 检查数据量波动
        self._check_data_fluctuation(current_snapshot)
    
    def _check_drop_database(self, deleted_dbs: Set[str]):
        """检查删库操作"""
        for db_name in deleted_dbs:
            should_alert, level = self.rule_engine.should_alert_for_drop_database(db_name)
            
            if not should_alert:
                logger.info(f"数据库 {db_name} 在白名单中，跳过告警")
                continue
            
            # 发送告警
            alert = Alert(
                alert_type=AlertType.DROP_DATABASE,
                level=getattr(AlertLevel, level.upper()),
                database=db_name,
                details={}
            )
            alert_manager.send_alert(alert)
            
            # 保存到数据库
            self.db_manager.save_alert(
                alert_type=AlertType.DROP_DATABASE,
                alert_level=level,
                database_name=db_name,
                alert_message=f"删除数据库: {db_name}"
            )
            
            logger.warning(f"检测到删除数据库: {db_name}")
    
    def _check_drop_table(self, deleted_tables: Set[str]):
        """检查删表操作"""
        now = time.time()
        
        for full_table_name in deleted_tables:
            db_name, table_name = full_table_name.split('.', 1)
            
            # 记录删除操作
            self.recent_deletes[full_table_name] = now
            self.recent_delete_list.append((full_table_name, now))
            
            # 判断是否需要告警
            should_alert, level = self.rule_engine.should_alert_for_drop_table(
                db_name, table_name
            )
            
            if not should_alert:
                logger.info(f"表 {full_table_name} 在白名单中，跳过告警")
                continue
            
            # 获取表详情
            table_details = self.last_snapshot['table_details'].get(full_table_name, {})
            
            # 发送告警
            alert = Alert(
                alert_type=AlertType.DROP_TABLE,
                level=getattr(AlertLevel, level.upper()),
                database=db_name,
                table=table_name,
                user=table_details.get('owner'),
                details={
                    '分区数': table_details.get('partition_count', 0),
                    '行数': table_details.get('row_count', 0),
                    '大小': self._format_size(table_details.get('total_size', 0))
                }
            )
            alert_manager.send_alert(alert)
            
            # 保存到数据库
            self.db_manager.save_alert(
                alert_type=AlertType.DROP_TABLE,
                alert_level=level,
                database_name=db_name,
                table_name=table_name,
                operation_user=table_details.get('owner'),
                details=alert.details,
                alert_message=f"删除表: {full_table_name}"
            )
            
            logger.warning(f"检测到删除表: {full_table_name}")
    
    def _check_table_overwrite(self, new_tables: Set[str]):
        """检查表覆盖操作"""
        rule = self.config.get('alert_rules', {}).get('table_overwrite', {})
        if not rule.get('enabled', True):
            return
        
        time_window = rule.get('time_window', 300)
        now = time.time()
        
        for full_table_name in new_tables:
            delete_time = self.recent_deletes.get(full_table_name)
            
            if delete_time and (now - delete_time) <= time_window:
                db_name, table_name = full_table_name.split('.', 1)
                
                alert = Alert(
                    alert_type=AlertType.TABLE_OVERWRITE,
                    level=AlertLevel.SEVERE,
                    database=db_name,
                    table=table_name,
                    details={
                        '时间间隔': f"{int(now - delete_time)}秒"
                    }
                )
                alert_manager.send_alert(alert)
                
                # 保存到数据库
                self.db_manager.save_alert(
                    alert_type=AlertType.TABLE_OVERWRITE,
                    alert_level='severe',
                    database_name=db_name,
                    table_name=table_name,
                    details=alert.details,
                    alert_message=f"表覆盖: {full_table_name}"
                )
                
                logger.warning(f"检测到表覆盖: {full_table_name}")
        
        # 清理过期记录
        self.recent_deletes = {k: v for k, v in self.recent_deletes.items()
                              if now - v <= time_window}
    
    def _check_batch_delete(self):
        """检查批量删除"""
        rule = self.config.get('alert_rules', {}).get('batch_delete', {})
        if not rule.get('enabled', True):
            return
        
        threshold = rule.get('threshold', 10)
        time_window = rule.get('time_window', 600)
        now = time.time()
        
        # 清理过期记录
        self.recent_delete_list = [(t, ts) for t, ts in self.recent_delete_list
                                   if now - ts <= time_window]
        
        if len(self.recent_delete_list) >= threshold:
            databases = set(t.split('.', 1)[0] for t, _ in self.recent_delete_list)
            
            alert = Alert(
                alert_type=AlertType.BATCH_DELETE,
                level=AlertLevel.CRITICAL,
                database=",".join(databases),
                details={
                    '删除表数': len(self.recent_delete_list),
                    '涉及数据库': len(databases)
                }
            )
            alert_manager.send_alert(alert)
            
            # 保存到数据库
            self.db_manager.save_alert(
                alert_type=AlertType.BATCH_DELETE,
                alert_level='critical',
                database_name=",".join(databases),
                details=alert.details,
                alert_message=f"批量删除 {len(self.recent_delete_list)} 张表"
            )
            
            logger.critical(f"检测到批量删除: {len(self.recent_delete_list)}张表")
            self.recent_delete_list.clear()
    
    def _check_data_fluctuation(self, current_snapshot: Dict[str, Any]):
        """检查数据量波动"""
        common_tables = (self.last_snapshot['tables'] & 
                        current_snapshot['tables'])
        
        for full_table_name in common_tables:
            old_details = self.last_snapshot['table_details'].get(full_table_name)
            new_details = current_snapshot['table_details'].get(full_table_name)
            
            if not old_details or not new_details:
                continue
            
            # 计算变化率
            partition_change_rate = None
            row_change_rate = None
            
            old_partitions = old_details.get('partition_count', 0)
            new_partitions = new_details.get('partition_count', 0)
            
            if old_partitions > 0:
                partition_change_rate = abs(new_partitions - old_partitions) / old_partitions
            
            old_rows = old_details.get('row_count', 0)
            new_rows = new_details.get('row_count', 0)
            
            if old_rows > 0:
                row_change_rate = abs(new_rows - old_rows) / old_rows
            
            # 判断是否需要告警
            should_alert, level, details = self.rule_engine.should_alert_for_data_fluctuation(
                *full_table_name.split('.', 1),
                partition_change_rate, row_change_rate
            )
            
            if should_alert:
                db_name, table_name = full_table_name.split('.', 1)
                
                alert = Alert(
                    alert_type=AlertType.DATA_FLUCTUATION,
                    level=getattr(AlertLevel, level.upper()),
                    database=db_name,
                    table=table_name,
                    details=details
                )
                alert_manager.send_alert(alert)
                
                # 保存到数据库
                self.db_manager.save_alert(
                    alert_type=AlertType.DATA_FLUCTUATION,
                    alert_level=level,
                    database_name=db_name,
                    table_name=table_name,
                    details=details,
                    alert_message=f"数据量波动: {full_table_name}"
                )
                
                logger.info(f"检测到数据量波动: {full_table_name}")
    
    def _format_size(self, size_bytes: int) -> str:
        """格式化文件大小"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.2f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.2f} PB"
    
    def get_status(self) -> Dict[str, Any]:
        """获取监控引擎状态"""
        return {
            'running': self.running,
            'last_check_time': datetime.now().isoformat(),
            'databases_count': len(self.last_snapshot['databases']),
            'tables_count': len(self.last_snapshot['tables']),
            'recent_deletes': len(self.recent_deletes),
            'recent_delete_list': len(self.recent_delete_list)
        }

